package se.webbzon.boltzmann.game.sequence;

import java.awt.Point;
import java.util.Stack;

import se.webbzon.boltzmann.environment.AbstractEnvironment;
import se.webbzon.boltzmann.environment.PlayerEnvironment;
import se.webbzon.boltzmann.game.object.dialogue.ChoiceDialogue;
import se.webbzon.boltzmann.game.object.dialogue.IntegerDialogue;
import se.webbzon.boltzmann.game.object.dialogue.ItemDialogue;
import se.webbzon.boltzmann.game.object.player.AbstractPlayerCharacter;
import se.webbzon.boltzmann.game.sequence.builder.AbstractSequenceBinding;
import se.webbzon.boltzmann.inventory.Inventory;
import se.webbzon.boltzmann.player.Player;
import se.webbzon.oschi01.worldobject.WorldObject;

public class Sequences {
	
	// The prefixes used for local and environment variables
	public static final String environmentPrefix = "$";
	public static final String localPrefix = "@";
	
	/*============================================================
	Public Methods
	============================================================*/
	
	/** Returns the provided variable name without the variable
	 * prefix. **/
	public static final String withoutPrefix(String str) {
		if (str.startsWith(environmentPrefix) || str.startsWith(localPrefix))
			return str.substring(1);
		else
			return str;
	}
	
	/** Returns the provided variable name with the variable
	 * prefix. **/
	public static final String withPrefix(String str, boolean local) {
		if (str.startsWith(localPrefix) || str.startsWith(environmentPrefix))
			return str;
		else if (local)
			return localPrefix + str;
		else
			return environmentPrefix + str;
	}
	
	/** Returns true if there is a variable defined with the given name. **/
	public static boolean isVariable(Sequence sequence, String name) {
		if (name.startsWith(environmentPrefix)) {
			AbstractEnvironment env = sequence.getEnvironment();
			if (env instanceof PlayerEnvironment) {
				PlayerEnvironment penv = (PlayerEnvironment) env;
				return penv.getVariables().isAssigned(withoutPrefix(name));
			} else
				return false;
		} else
			return sequence.getVariables().isAssigned(withoutPrefix(name));
	}
	
	/** Sets the variable with the given name to the provided string value. **/
	public static void set(Sequence sequence, String name, String value) {
		if (name.startsWith(environmentPrefix)) {
			AbstractEnvironment env = sequence.getEnvironment();
			if (env instanceof PlayerEnvironment) {
				PlayerEnvironment penv = (PlayerEnvironment) env;
				penv.getVariables().assign(withoutPrefix(name), value);
			}
		} else
			sequence.getVariables().assign(withoutPrefix(name), value);
	}
	
	/** Sets the variable with the given name to the provided double value. **/
	public static void set(Sequence sequence, String name, Double value) {
		if (name.startsWith(environmentPrefix)) {
			AbstractEnvironment env = sequence.getEnvironment();
			if (env instanceof PlayerEnvironment) {
				PlayerEnvironment penv = (PlayerEnvironment) env;
				penv.getVariables().assign(withoutPrefix(name), value);
			}
		} else
			sequence.getVariables().assign(withoutPrefix(name), value);
	}
	
	/** Parses the provided string into a value. **/
	public static final Object parse(Sequence sequence, String str) {
		String name = null;
		if (str.startsWith(localPrefix)) try {
			// Read predefined variable
			name = str.substring(1).toUpperCase();
			if (name.isEmpty()) 
				return null;
			Key key = Key.valueOf(name);
			switch (key) {
			case CHARACTER: 
				return getPlayer(sequence).getCharacter().getName();
			case COINS:
				return (double) getPlayer(sequence).getCoins().getCoins();
			case HEALTH: 
				return (double) getPlayer(sequence).getHealth().getHealth();
			case LAST_SELECTION:
				return (double) lastChoiceDialogue(sequence).getSelection();
			case LAST_INTEGER:
				return (double) lastIntegerDialogue(sequence).getValue();
			case LAST_CHOICE:
				return lastChoiceDialogue(sequence).getChoice();
			case LAST_ITEM_INDEX:
				Point p = new Point();
				lastItemDialogue(sequence).getItemPosition(p);
				Inventory inv = getPlayer(sequence).getInventory();
				return p.getY() + inv.getColumns() * p.getX();
			case LAST_ITEM_NAME:
				return lastItemDialogue(sequence).getItem().getName();
			}
			
		} catch (IllegalArgumentException e) {
			return sequence.getVariables().valueOf(name);
			
		} else if (str.startsWith(environmentPrefix)) {
			name = str.substring(1);
			// Read value from environment variable
			AbstractEnvironment env = sequence.getEnvironment();
			if (env instanceof PlayerEnvironment) {
				PlayerEnvironment penv = (PlayerEnvironment) env;
				if (penv.getVariables().isAssigned(name)) {
					return penv.getVariables().valueOf(name);
				}
			}
		}
		
		// The provided string is not a variable
		try {
			return Double.parseDouble(str);
		} catch (NumberFormatException e) {
			return str;
		}
	}
	
	/*============================================================
	Private Methods
	============================================================*/
	
	/** Returns the player associated with the given sequence. **/
	private static final Player getPlayer(Sequence sequence) {
		WorldObject wso = 
				sequence.getBinding(AbstractSequenceBinding.PLAYER);
		if (wso == null)
			return null;
		else
			return ((AbstractPlayerCharacter) wso).getPlayer();
	}
	
	/** Returns the last choice dialogue in the sequence. **/
	private static final ChoiceDialogue lastChoiceDialogue(Sequence sequence) {
		Stack<SequenceNode> stack = sequence.getStack();
		for (int i = stack.size()-1; i >= 0; i--) {
			SequenceNode node = stack.get(i);
			if (node instanceof ChoiceDialogueSequenceNode) {
				ChoiceDialogueSequenceNode dialogueNode = 
						(ChoiceDialogueSequenceNode) node;
				return (ChoiceDialogue) dialogueNode.getDialogue();
			}
		}
		return null;
	}
	
	/** Returns the last integer dialogue in the sequence. **/
	private static final IntegerDialogue lastIntegerDialogue(Sequence sequence) {
		Stack<SequenceNode> stack = sequence.getStack();
		for (int i = stack.size()-1; i >= 0; i--) {
			SequenceNode node = stack.get(i);
			if (node instanceof IntegerDialogueSequenceNode) {
				IntegerDialogueSequenceNode dialogueNode = 
						(IntegerDialogueSequenceNode) node;
				return (IntegerDialogue) dialogueNode.getDialogue();
			}
		}
		return null;
	}
	
	/** Returns the last item dialogue in the sequence. **/
	private static final ItemDialogue lastItemDialogue(Sequence sequence) {
		Stack<SequenceNode> stack = sequence.getStack();
		for (int i = stack.size()-1; i >= 0; i--) {
			SequenceNode node = stack.get(i);
			if (node instanceof ItemDialogueSequenceNode) {
				ItemDialogueSequenceNode dialogueNode = 
						(ItemDialogueSequenceNode) node;
				return (ItemDialogue) dialogueNode.getDialogue();
			}
		}
		return null;
	}
	
	/*============================================================
	Private Enumerations
	============================================================*/
		
	private static enum Key {
		
		// The available values
		CHARACTER, COINS, HEALTH, 
		LAST_SELECTION, LAST_CHOICE, LAST_INTEGER, LAST_ITEM_INDEX,
		LAST_ITEM_NAME;
		
	};

}
